#include "globals.h"
#include "errno.h"

#include "util/debug.h"

#include "proc/kthread.h"
#include "proc/kmutex.h"


/*** PRIVATE KTQUEUE MANIPULATION FUNCTIONS ***/
/**
 * Enqueues a thread onto a queue.
 *
 * @param q the queue to enqueue the thread onto
 * @param thr the thread to enqueue onto the queue
 */
void
enqueue(ktqueue_t *q, kthread_t *thr)
{
        KASSERT(!thr->kt_wchan);
        list_insert_head(&q->tq_list, &thr->kt_qlink);
        thr->kt_wchan = q;
        q->tq_size++;
}

/**
 * Dequeues a thread from the queue.
 *
 * @param q the queue to dequeue a thread from
 * @return the thread dequeued from the queue
 */
kthread_t *
dequeue(ktqueue_t *q)
{
        kthread_t *thr;
        list_link_t *link;

        if (list_empty(&q->tq_list))
                return NULL;

        link = q->tq_list.l_prev;
        thr = list_item(link, kthread_t, kt_qlink);
        list_remove(link);
        thr->kt_wchan = NULL;

        q->tq_size--;

        return thr;
}

/*
 * IMPORTANT: Mutexes can _NEVER_ be locked or unlocked from an
 * interrupt context. Mutexes are _ONLY_ lock or unlocked from a
 * thread context.
 */

void
kmutex_init(kmutex_t *mtx)
{
        /*NOT_YET_IMPLEMENTED("PROCS: kmutex_init"); */
	sched_queue_init(&mtx->km_waitq);
        mtx->km_holder = NULL;
}

/*
 * This should block the current thread (by sleeping on the mutex's
 * wait queue) if the mutex is already taken.
 *
 * No thread should ever try to lock a mutex it already has locked.
 */
void
kmutex_lock(kmutex_t *mtx)
{
	KASSERT(curthr && (curthr != mtx->km_holder));
        /*NOT_YET_IMPLEMENTED("PROCS: kmutex_lock");*/
	if(curthr == mtx->km_holder) 
        return;	
  
	if(mtx->km_holder!=NULL)
	{

		curthr->kt_state = KT_SLEEP;

		enqueue(&mtx->km_waitq, curthr);
/* dbg_print("consumer length of specialq lock\n");*/
		sched_switch();

	}
	else
	{
		
		mtx->km_holder = curthr;
        }

}

/*
 * This should do the same as kmutex_lock, but use a cancellable sleep
 * instead.
 */
int
kmutex_lock_cancellable(kmutex_t *mtx)
{
        /*NOT_YET_IMPLEMENTED("PROCS: kmutex_lock_cancellable");*/
	KASSERT(curthr && (curthr != mtx->km_holder));
	if( curthr->kt_cancelled == 1)
        return -EINTR;
        
	if(mtx->km_holder == curthr) 
	return 0;
	if(mtx->km_holder!=NULL)
	{
		curthr->kt_state = KT_SLEEP_CANCELLABLE;
		enqueue(&mtx->km_waitq, curthr);
		sched_switch();
	}
	else
	{
		mtx->km_holder = curthr;
        }	
        return 0;
}

/*
 * If there are any threads waiting to take a lock on the mutex, one
 * should be woken up and given the lock.
 *
 * Note: This should _NOT_ be a blocking operation!
 *
 * Note: Don't forget to add the new owner of the mutex back to the
 * run queue.
 *
 * Note: Make sure that the thread on the head of the mutex's wait
 * queue becomes the new owner of the mutex.
 *
 * @param mtx the mutex to unlock
 */
void
kmutex_unlock(kmutex_t *mtx)
{
        /*NOT_YET_IMPLEMENTED("PROCS: kmutex_unlock");*/
	KASSERT(curthr && (curthr == mtx->km_holder));
	if(sched_queue_empty(&mtx->km_waitq))
        mtx->km_holder = NULL;
	else
	{
		kthread_t *newthr = NULL;
        newthr = dequeue(&mtx->km_waitq);
		mtx->km_holder = newthr;
		sched_make_runnable(newthr);
/*		 dbg_print("consumer length of specialq unlock\n");*/
        }
       KASSERT(curthr != mtx->km_holder); 
	
}
